#pragma once

#include"Thread.hpp"
#include"Task.hpp"
#include<queue>
#include<vector>
#include<unistd.h>
#include"Log.hpp"

using namespace zzx;

template<typename T>
class ThreadPool
{
public:
    //加锁
    void LockQueue() 
    {
        pthread_mutex_lock(&_mutex);
    }

    //解锁
    void UnLockQueue() 
    {
        pthread_mutex_unlock(&_mutex);
    }

    //等待条件满足
    void WaitQueue() 
    {
        pthread_cond_wait(&_cond, &_mutex);
    }

    //唤醒一个等待线程
    void WakeOneThread() 
    {
        pthread_cond_signal(&_cond);
    }

    //唤醒所有等待线程
    void WakeAllThread() 
    {
        pthread_cond_broadcast(&_cond);
    }

    //构建线程池
    ThreadPool(int num) 
        :_num(num), _waitnum(0),_isrunning(false)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
        LOG(Info, "ThreadPool Construct()");
        sleep(1);
    }

    //线程执行的任务函数
    void HandlerTask(string name) 
    {
        LOG(Info, "%s is running...", name.c_str());
        sleep(1);
        while(true)
        {
            LockQueue();
            //a.任务队列中无任务,且线程池正在运行
            while(_task_queue.empty() && _isrunning) 
            {
                ++_waitnum;
                WaitQueue(); //线程等待任务,将线程放在环境变量队列中等待
                --_waitnum;
            }
            //b.任务队列中无任务,且线程池已退出,则线程退出
            if(_task_queue.empty() && !_isrunning)
            {
                UnLockQueue();
                break;
            }
            //c.任务队列中有任务,但线程池已退出,线程处理完队列中剩余的任务再退出
            Task t;
            t = _task_queue.front(); //获取任务 
            _task_queue.pop();
            LOG(Debug, "%s get a task", name.c_str());

            /*在处理任务前解锁：是确保取出的任务在当前线程中被独占处理
            解锁是释放对共享资源(任务队列)的访问权限
            当线程取出一个任务后,此任务已经是当前线程的上下文(私有的)*/
            UnLockQueue(); //确保取出的任务在当前线程中被独占处理
            sleep(1);  

            /*处理任务  
            注意：任务类型既可以是函数对象(任务处理函数的地址)、也可以是仿函数
            (类中重载了operator()运算符)*/
            t(); 
            LOG(Debug, "%s handler a task, result is: %s", name.c_str(), t.ResultToString().c_str());
            sleep(1);
        }
    }

    //向任务队列中添加任务
    void EnTask(Task& t) 
    {
        LockQueue();
        if(_isrunning) //线程池正在运行
        {
            _task_queue.push(t); //任务入队列
            LOG(Info, "enqueue task success,%s", t.DebugToString().c_str());

            sleep(1);
            if(_waitnum > 0)
                WakeOneThread(); //唤醒队列
        }
        UnLockQueue();
    }

    //初始化线程池
    void InitThreadPool() 
    {
        for(int i = 0; i < _num; i++)
        {
            string name = "thread-" + to_string(i + 1);


            //注意1: 类中的成员函数，也可以是另一个类的回调方法,方便我们进行类级别的相互调用

            /*注意二: bind是一个标准库函数,用于将可调用对象(如:普通函数、成员函数)和参数绑定绑在一起,生成一个新的可调用对象
            (这个新的可调用对象就像调用原始函数一样,但其中某些或部分参数已经被预先固定)、
            绑定成员函数,第一个参数需要被指定为函数对象的指针(this指针);
            占位符:用来指定哪些参数在调用时需要传递,_1表示第一个参数*/
            thread td(std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1), name); //*
            
            _threads.push_back(td); 
            LOG(Info, "init %s done", td.name().c_str());
            sleep(1);
        }
    }

    //线程启动
    void Start()
    {
        _isrunning = true;
        for(auto& td : _threads)
        {
            td.start();
        }
    }

    //线程池退出
    void Stop() 
    {
        //因为_isrunning变量在临界资源中被使用了,确保了在同一时刻,只有一个线程能够修改线程的状态
        LockQueue();
        _isrunning = false;
        WakeAllThread(); //唤醒所有等待线程
        UnLockQueue();
    }

    //等待线程退出,回收资源,避免造成内存泄漏
    void WaitThread()
    {
        for(auto& td : _threads)
        {
            td.join();
            LOG(Info, "%s is quit...",td.name().c_str());
            sleep(1);
        }
    }

    //析构函数
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

private:
    int _num;  //预先创建线程的总数
    vector<thread> _threads;  //存储线程
    queue<T> _task_queue;  //任务队列,共享资源
    pthread_mutex_t _mutex;  //保护任务队列的互斥锁,互斥
    pthread_cond_t _cond;  //环境变量,同步
    int _waitnum; //在环境变量的队列中,等待任务的线程总数
    bool _isrunning; //线程池是否运行
};